/**
 * 实现一个简单的Gooch风格着色器，
 * 支持Directional Light、Point Light和Spot Light
 */
Shader "RTR/SB_Lit"
{
    Properties
    {
        
    }
    SubShader
    {
        Pass
        {
            Name "Gooch"
//            Tags 
//            {
//                "LightMode" = "Gooch"
//            }
            
            CGPROGRAM

            #pragma target 5.0
            #pragma vertex Vert
            #pragma fragment Frag

            // Uniform data
            // 一些矩阵
            float4x4 objectToWorldMatrix;
            float4x4 worldToObjectMatrix;
            float4x4 mvpMatrix;

            // Camera
            float3 cameraPosition;
            
            // Directional Light
            float3 directionalLightDirection;
            float3 directionalLightColor;

            // Point Light
            float3 pointLightPosition;
            float3 pointLightColor;
            float pointLightMinDistance;
            float pointLightMaxDistance;

            // Spot Light
            float3 spotLightPosition;
            float3 spotLightColor;
            float3 spotLightDir;
            float spotLightMinDistance;
            float spotLightMaxDistance;
            float spotLightCosP;
            float spotLightCosU;

            // Point Light

            // Material
            float3 color;

            // vertex data
            struct Attributes
            {
                float3 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float3 positionWS : VAR_POSITION;
                float3 normalWS : NORMAL;
                float3 color : COLOR;
            };

            // Vertex Shader Entry
            Varyings Vert(Attributes input)
            {
                Varyings output;
                output.positionWS = mul(objectToWorldMatrix, float4(input.positionOS, 1.0));
                output.positionCS = mul(mvpMatrix, float4(input.positionOS, 1.0));
                // M_IT * n => n * M_I
                output.normalWS = normalize(mul(input.normalOS, (float3x3)worldToObjectMatrix));
                output.color = color;
                return output;
            }

            float3 GetDirectionalLight(float3 p, float3 n, float3 v, float3 cWarm, float3 cHighlight)
            {
                float3 l = -directionalLightDirection;
                float3 r = reflect(directionalLightDirection, n);
                float s = saturate(100.0 * dot(r, v) - 97.0);
                return max(0.0, dot(n, l)) * directionalLightColor * lerp(cWarm, cHighlight, s);
            }

            float3 GetPointLight(float3 p, float3 n, float3 v, float3 cWarm, float3 cHighlight)
            {
                float3 lDir = pointLightPosition - p;
                float3 d2 = dot(lDir, lDir);
                float3 l = lDir / sqrt(d2);
                float3 r = reflect(-l, n);
                float s = saturate(100.0 * dot(r, v) - 97.0);
                float3 cLight = pointLightColor * (1 / max(d2, pointLightMinDistance * pointLightMinDistance)) *
                    pow(saturate(1.0 - d2 * d2 / pow(pointLightMaxDistance, 4)), 2.0);
                return max(0.0, dot(n, l)) * cLight * lerp(cWarm, cHighlight, s);;
            }

            float3 GetSpotLight(float3 p, float3 n, float3 v, float3 cWarm, float3 cHighlight)
            {
                float3 lDir = spotLightPosition - p;
                float3 d2 = dot(lDir, lDir);
                float3 l = lDir / sqrt(d2);
                float3 r = reflect(-l, n);
                float s = saturate(100.0 * dot(r, v) - 97.0);
                float3 cLight = spotLightColor * (1 / max(d2, spotLightMinDistance * spotLightMinDistance)) *
                    pow(saturate(1.0 - d2 * d2 / pow(spotLightMaxDistance, 4)), 2.0);
                float cosS = dot(spotLightDir, -l);
                float t = saturate((cosS - spotLightCosU) / (spotLightCosP - spotLightCosU));
                return max(0.0, dot(n, l)) * cLight * lerp(cWarm, cHighlight, s) * t * t;
            }

            float3 GetLit(float3 n, float3 v, Varyings input)
            {
                float3 cWarm = float3(0.3, 0.3, 0.0) + 0.25 * input.color;
                float3 cHighlight = float3(2.0, 2.0, 2.0);
                
                float3 lit = float3(0.0, 0.0, 0.0);
                lit += GetDirectionalLight(input.positionWS, n, v, cWarm, cHighlight);
                lit += GetPointLight(input.positionWS, n, v, cWarm, cHighlight);
                lit += GetSpotLight(input.positionWS, n, v, cWarm, cHighlight);
                return lit;
            }

            float3 GetUnlit(float3 n, float3 v, Varyings input)
            {
                float3 cCool = float3(0.0, 0.0, 0.55) + 0.25 * input.color;
                return 0.5 * cCool;
            }

            // Fragment Shader Entry
            float4 Frag(Varyings input) : SV_TARGET
            {
                // Shading Model is: c = F_unlit + \sum{dot(n, l_i) * cLight_i * F_lit}
                // F_unlit = 0.5 * cCool
                // F_lit = lerp(cWarm, CHighlight, s)
                // s = saturate(100 * dot(r, v) - 97 just a specular factor, similarly dot(r, v)^n
                // cCool = (0.0, 0.0, 0.55) + 0.25 * cSurface
                // cWarm = (0.3, 0.3, 0.0) + 0.25 * cSurface
                // CHighlight = (2.0, 2.0, 2.0) 

                // First, calc intermediate value
                float3 v = normalize(cameraPosition - input.positionWS);
                float3 n = normalize(input.normalWS);
                // Second, calc unlit part
                float3 unlit = GetUnlit(n, v, input);
                // Third, calc lit part
                float3 lit = GetLit(n, v, input);
                return float4(unlit + lit, 1.0);
            }
            
            ENDCG
        }   
        
        Pass
        {
            Name "Normal"
            
            Tags 
            {
                "LightMode" = "Normal"
            }
            
            CGPROGRAM

            #pragma target 5.0
            #pragma require geometry
            #pragma vertex Vert
            #pragma geometry Geo
            #pragma fragment Frag

            // Uniform data
            // 一些矩阵
            float4x4 objectToWorldMatrix;
            float4x4 worldToObjectMatrix;
            float4x4 mvpMatrix;

            struct Attributes
            {
                float3 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Intermediates
            {
                float3 positionOS : VAR_POSITION;
                float3 normalOS : VAR_NORMAL;
            };

            struct Varyings
            {
                float3 normalWS : VAR_NORMAL;
                float4 positionCS : SV_POSITION;
            };

            Intermediates Vert(Attributes input)
            {
                Intermediates output;
                output.positionOS = input.positionOS;
                output.normalOS = input.normalOS;
                return output;
            }

            [maxvertexcount(2)]
            void Geo(triangle Intermediates inputs[3], inout LineStream<Varyings> lineStream)
            {
                // 取其中一个顶点的法线
                float3 normal = normalize(inputs[0].normalOS);
                // 将三角形的重心作为法线段的起点，沿着这个法线方向增加一个新的顶点
                float3 v0 = inputs[0].positionOS;
                float3 v1 = inputs[1].positionOS;
                float3 v2 = inputs[2].positionOS;
                Varyings output;
                float3 o = v0 + (v1 - v0) * 0.333333333333 + (v2 - v0) * 0.333333333333;
                output.normalWS = mul(inputs[0].normalOS, worldToObjectMatrix);
                output.positionCS = mul(mvpMatrix, float4(o, 1.0));
                lineStream.Append(output);
                float3 p = o + 0.5 * normal;
                output.normalWS = mul(inputs[0].normalOS, worldToObjectMatrix);
                output.positionCS = mul(mvpMatrix, float4(p, 1.0));
                lineStream.Append(output);
            }

            float4 Frag(Varyings input) : SV_TARGET
            {
                return float4(normalize(input.normalWS), 1.0);
            }
            
            ENDCG
        } 
    }
}
